package cn.fufeiii.bt.threadpool;

import lombok.Data;

import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 可以重试的链表阻塞队列
 * <p>
 * 应用场景：结合线程池的CallerRuns策略，保证一定的消费速率处理任务 或者说 保证尽量使用线程池的线程处理任务
 * 所以此场景下，若超过重试次数还入队失败了，则会使用CallerRuns的拒绝策略
 *
 * @author FuFei
 */
public class RetryLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {

    private final RetryConfig retryConfig;

    public RetryLinkedBlockingQueue(RetryConfig retryConfig) {
        this.retryConfig = retryConfig;
    }

    public RetryLinkedBlockingQueue(int capacity, RetryConfig retryConfig) {
        super(capacity);
        this.retryConfig = retryConfig;
    }

    public RetryLinkedBlockingQueue(Collection<? extends E> c, RetryConfig retryConfig) {
        super(c);
        this.retryConfig = retryConfig;
    }


    @Override
    public boolean offer(E e) {
        boolean offerResult;
        int currentRetryCount = 0;

        do {
            // 尝试入队
            offerResult = super.offer(e);

            // 入队失败则计算重试次数和进行休眠
            if (!offerResult) {
                currentRetryCount++;
                try {
                    retryConfig.timeUnit.sleep(retryConfig.sleepTime);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            }

            // 若入队失败且重试次数小于等于最大重试次数才进入下一次offer
        } while (!offerResult && currentRetryCount <= retryConfig.maxRetryCount);

        return offerResult;
    }

    /**
     * 重试配置信息
     */
    @Data
    public static class RetryConfig {

        /**
         * 最大重试次数
         */
        private final int maxRetryCount;

        /**
         * 睡眠时间
         */
        private final int sleepTime;

        /**
         * 超时时间
         */
        private final TimeUnit timeUnit;

        public RetryConfig() {
            this(3, 100, TimeUnit.MILLISECONDS);
        }

        public RetryConfig(int maxRetryCount, int sleepTime, TimeUnit timeUnit) {
            this.maxRetryCount = maxRetryCount;
            this.sleepTime = sleepTime;
            this.timeUnit = timeUnit;
            Objects.requireNonNull(timeUnit);
        }

    }

}
